﻿using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using Kaleida.ServiceMonitor.Framework;

namespace Kaleida.ServiceMonitor.Model.Statistics
{
    public class ProcessingStatistics
    {
        private readonly string name;
        private readonly string description;
        private readonly AggregateDurationCalculator processingAggregate = new AggregateDurationCalculator();
        private int successCount;
        private int failureCount;
        private DateTime? startTime;
        private Dictionary<int,int> millisecondCounts = new Dictionary<int, int>();

        public ProcessingStatistics(string name, string description)
        {
            this.name = name;
            this.description = description;
            Reset();
        }

        public string Name
        {
            get { return name; }
        }

        public string Description
        {
            get { return description; }
        }

        public void IncrementSuccessCount(TimeSpan duration)
        {
            lock (this)
            {
                if (startTime == null) Start();

                if (duration.Ticks < 0)
                    throw new ArgumentOutOfRangeException("duration", duration.Ticks, "Duration cannot be negative");

                successCount++;
                processingAggregate.AddResult(duration);

                var distributionBucket = (int) duration.TotalMilliseconds;

                if (distributionBucket == 0)
                    distributionBucket = 1;

                if (millisecondCounts.ContainsKey(distributionBucket))
                {
                    millisecondCounts[distributionBucket]++;
                }
                else
                {
                    millisecondCounts.Add(distributionBucket, 1);
                }
            }
        }

        public void IncrementFailureCount()
        {
            if (startTime == null) Start();
            failureCount++;
        }

        public void Start()
        {
            startTime = DateTime.Now;
        }

        public void Reset()
        {
            lock (this)
            {
                startTime = null;
                processingAggregate.Reset();
                successCount = 0;
                failureCount = 0;

                millisecondCounts = new Dictionary<int, int>();
            }
        }

        public DateTime? StartTime
        {
            get { return startTime; }
        }

        public int ProcessedCount
        {
            get { return successCount + failureCount; }
        }

        public int SuccessCount
        {
            get { return successCount; }
        }

        public int FailureCount
        {
            get { return failureCount; }
        }

        [CanBeNull]
        internal Percentage GetSuccessRate()
        {
            if(ProcessedCount == 0)
                return null;

            return Percentage.FromDecimal((decimal) SuccessCount/ProcessedCount);
        }

        public TimeSpan? MeanProcessingTime
        {
            get { return processingAggregate.GetMean(); }
        }

        public TimeSpan? MinimumProcessingTime
        {
            get { return processingAggregate.GetMinimum(); }
        }

        public TimeSpan? MaximumProcessingTime
        {
            get { return processingAggregate.GetMaximum(); }
        }

        public TimeSpan? MostRecentProcessingTime
        {
            get { return processingAggregate.GetMostRecent(); }
        }

        public IList<ResponseTimePercentile> GetResponseTimePercentiles(IList<decimal> percentages)
        {
            lock (this)
            {
                List<KeyValuePair<int, int>> countsCopy = millisecondCounts.ToList();

                return percentages.Select(i => new ResponseTimePercentile(i, GetResponseTimePercentile(countsCopy, i))).ToList();
            }
        }

        private static TimeSpan? GetResponseTimePercentile(List<KeyValuePair<int, int>> counts, decimal percentage)
        {
            int totalCount = counts.Sum(i=>i.Value);

            int runningTotal = 0;
            foreach (var bucket in counts.OrderBy(i=>i.Key))
            {
                runningTotal += bucket.Value;

                var percentageSoFar = (runningTotal * 100m) / totalCount;
                if (percentageSoFar >= percentage)
                    return TimeSpan.FromMilliseconds(bucket.Key);
            }

            return null;
        }

        public IList<DistributionPoint> GetResponseTimeDistribution()
        {
            lock (this)
            {
                return millisecondCounts.OrderBy(i => i.Key).Select(i => new DistributionPoint(i.Key, i.Value)).ToList();
            }
        }
    }
}
